package com.freewater.javabase.demo.mutithread.synchronize;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 前面讲到的ReentrantLock保证了只有一个线程可以执行临界区代码：
 *
 * <pre>
 * public class Counter {
 *     private final Lock lock = new ReentrantLock();
 *     private int[] counts = new int[10];
 *
 *     public void inc(int index) {
 *         lock.lock();
 *         try {
 *             counts[index] += 1;
 *         } finally {
 *             lock.unlock();
 *         }
 *     }
 *
 *     public int[] get() {
 *         lock.lock();
 *         try {
 *             return Arrays.copyOf(counts, counts.length);
 *         } finally {
 *             lock.unlock();
 *         }
 *     }
 * }
 * </pre>
 * 但是有些时候，这种保护有点过头。因为我们发现，任何时刻，只允许一个线程修改，也就是调用inc()方法是必须获取锁，但是，get()方法只读取数据，不修改数据，它实际上允许多个线程同时调用。
 * <p>
 * 实际上我们想要的是：允许多个线程同时读，但只要有一个线程在写，其他线程就必须等待(不管是写线程还是读线程)：
 *
 * <pre>
 *      读	    写
 * 读	允许	    不允许
 * 写	不允许	不允许
 * </pre>
 */
public class ReadWriteLockDemo {

    public static void main(String[] args) {
        Counter counter = new Counter();

        Thread writeThread = new Thread(() -> {
            Random random = new Random();
            for (int i = 0; i < 10; i++) {
                int index = random.nextInt(10);
                counter.inc(index);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        writeThread.start();

        List<Thread> readThreads = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    int[] ints = counter.get();
                    StringBuilder sb = new StringBuilder();
                    for (int item : ints) {
                        sb.append(item).append(" ");
                    }
                    System.out.println("Thread " + Thread.currentThread().threadId() + ": " + sb);
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        break;
                    }
                }
            });
            thread.start();
            readThreads.add(thread);
        }
    }

    public static class Counter {
        private final int[] counts = new int[10];

        private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

        private final Lock readLock = rwLock.readLock();
        private final Lock writeLock = rwLock.writeLock();

        public void inc(int index) {
            writeLock.lock();
            try {
                counts[index] += 1;
                Thread.sleep(1000);// 模拟耗时的写入过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
            }
        }

        public int[] get() {
            readLock.lock();
            try {
                return Arrays.copyOf(counts, counts.length);
            } finally {
                readLock.unlock();
            }
        }
    }
}
